#include <cstdio>
#include <cstring>
#include <openssl/ssl.h>
#include <openssl/rand.h>
//#include <openssl/ecdsa.h>

#define BUF_SIZE			4096
#define AES_256_BLOCK_SIZE	32

int main3() {
	// uninitialized symmetric cipher context ----
	EVP_CIPHER_CTX* ctx = new EVP_CIPHER_CTX;

	// symmetric cipher ----
	const EVP_CIPHER* type = EVP_aes_256_cbc();

	unsigned char
			message[BUF_SIZE] =
					"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	printf("Unencrypted string = {%s}\n", message);

	int npubk = 1;
	unsigned char** ek = new unsigned char*[npubk];
	int* ekl = new int[npubk];
	EVP_PKEY** pubk = new EVP_PKEY*[npubk];

	// read in pem-encoded public key ----
	BIO* rsa_pub_bio = BIO_new_file("rsapublickey.pem", "r");
	RSA* rsa_pub = RSA_new();
	PEM_read_bio_RSA_PUBKEY(rsa_pub_bio, &rsa_pub, NULL, NULL);
	BIO_free(rsa_pub_bio);

	// encrypt symmetric session keys ----
	for (int i = 0; i < npubk; i++) {
		pubk[i] = EVP_PKEY_new();
		EVP_PKEY_assign_RSA(pubk[i], rsa_pub);
		ek[i] = new unsigned char[EVP_PKEY_size(pubk[i])];
		ekl[i] = EVP_PKEY_size(pubk[i]);
	}

	// random initialization vector ----
	unsigned char* iv = new unsigned char[EVP_MAX_IV_LENGTH];
	RAND_pseudo_bytes(iv, EVP_MAX_IV_LENGTH);

	int message_len;	// initialized by EVP_SealUpdate & EVP_SealFinal
	unsigned char encrypt_buf[BUF_SIZE + AES_256_BLOCK_SIZE];

	EVP_SealInit(ctx, type, ek, &ekl[0], &iv[0], &pubk[0], npubk);
	// EVP_SealUpdate changes message_len to # bytes in message ----
	EVP_SealUpdate(ctx, encrypt_buf, &message_len, message, strlen((const char*) message));
	printf("buf_len: %d\n", message_len);
	int total_len = message_len;	// line must be between SealUpdate & SealFinal
	// EVP_SealFinal changes message_len value to # bytes of encryption overhead ----
	EVP_SealFinal(ctx, &encrypt_buf[message_len], &message_len);

	int i;
	printf("Encrypted string = {");
	for (i = 0; i < message_len; i++) {
		printf("%02x", encrypt_buf[i]);
	}

	for (i = 0; i < message_len; i++) {
		printf("%02x", encrypt_buf[i + total_len]);
	}
	printf("}\n");

	unsigned char decrypt_buf[BUF_SIZE];
	int decrypt_len;	// initialized by EVP_OpenUpdate & EVP_OpenFinal

	// read in pem-encoded encrypted private key ----
	BIO* rsa_priv_bio = BIO_new_file("rsaprivatekey.pem", "r");
	RSA* rsa_priv = RSA_new();
	PEM_read_bio_RSAPrivateKey(rsa_priv_bio, &rsa_priv, NULL, NULL);
	BIO_free(rsa_priv_bio);

	EVP_PKEY* privk = EVP_PKEY_new();
	EVP_PKEY_assign_RSA(privk, rsa_priv);

	EVP_OpenInit(ctx, type, *ek, ekl[0], &iv[0], privk);
	// EVP_OpenUpdate changes decrypt_len to # bytes in decrypted message ----
	EVP_OpenUpdate(ctx, decrypt_buf, &decrypt_len, encrypt_buf, total_len + message_len);
	total_len = decrypt_len;	// line must be between OpenUpdate & OpenFinal
	EVP_OpenFinal(ctx, &decrypt_buf[total_len], &decrypt_len);
	// EVP_OpenFinal changes decrypt_len value to # bytes of encryption overhead ----
	decrypt_buf[total_len + decrypt_len] = '\0';

	printf("Unencrypted string = {%s}\n", decrypt_buf);

	delete ctx;
	EVP_PKEY_free(privk);
	for (int i = 0; i < npubk; i++) {
		EVP_PKEY_free(pubk[i]);
		delete ek[i];
	}
	delete[] ek;
	delete[] ekl;
	delete[] pubk;
	delete[] iv;
	EVP_cleanup();

	return 0;
}
